﻿//"The contents of this file are subject to the Mozilla Public License
//Version 1.1 (the "License"); you may not use this file except in
//compliance with the License. You may obtain a copy of the License at
//http://www.mozilla.org/MPL/

//Software distributed under the License is distributed on an "AS IS"
//basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//License for the specific language governing rights and limitations
//under the License.

//The Original Code is TSharp.

//The Initial Developer of the Original Code is Mar3ek (http://mar3ek.wordpress.com).
//Portions created by Mar3ek are Copyright (C) 2010
//Mar3ek (http://mar3ek.wordpress.com). All Rights Reserved.

//Contributor(s): ______________________________________.

//Alternatively, the contents of this file may be used under the terms
//of the _____ license (the  "[___] License"), in which case the
//provisions of [______] License are applicable instead of those
//above.  If you wish to allow use of your version of this file only
//under the terms of the [____] License and not to allow others to use
//your version of this file under the MPL, indicate your decision by
//deleting  the provisions above and replace  them with the notice and
//other provisions required by the [___] License.  If you do not delete
//the provisions above, a recipient may use your version of this file
//under either the MPL or the [___] License."

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;

namespace TSharp
{
    public partial class Twitter
    {
        #region Friendships

        #region FollowUser
        /// <summary>
        /// Allows the authenticating users to follow the specified user.
        /// </summary>
        /// <param name="screenName">The screen name of the user to follow</param>
        /// <param name="allowDeviceNotifications">Enable device notifications for the target user</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>Followed user</returns>
        public TwitterUser FollowUser(string screenName, bool allowDeviceNotifications)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            var result = SendRequest(RequestMethod.Post,
                                        "https://api.twitter.com/1/friendships/create.xml",
                                        false,
                                        new KeyValuePair<string, string>("screen_name", screenName),
                                        new KeyValuePair<string, string>("follow", allowDeviceNotifications.ToString()));
            var doc = XDocument.Parse(result);

            return ParseAuthor(doc.Element("user"));
        }

        /// <summary>
        /// Allows the authenticating users to follow the specified user.
        /// </summary>
        /// <param name="userId">The ID of the user to follow</param>
        /// <param name="allowDeviceNotifications">Enable device notifications for the target user</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>Followed user</returns>
        public TwitterUser FollowUser(ulong userId, bool allowDeviceNotifications)
        {
            if (userId < 1) throw new ArgumentOutOfRangeException("userId");

            var result = SendRequest(RequestMethod.Post,
                                        "https://api.twitter.com/1/friendships/create.xml",
                                        false,
                                        new KeyValuePair<string, string>("user_id", userId.ToString()),
                                        new KeyValuePair<string, string>("follow", allowDeviceNotifications.ToString()));
            var doc = XDocument.Parse(result);

            return ParseAuthor(doc.Element("user"));
        }

        /// <summary>
        /// Allows the authenticating users to follow the specified user.
        /// </summary>
        /// <param name="user">The user whom to follow</param>
        /// <param name="allowDeviceNotifications">Enable device notifications for the target user</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>Followed user</returns>
        public TwitterUser FollowUser(TwitterUser user, bool allowDeviceNotifications)
        {
            if (user == null) throw new ArgumentNullException("user");
            var result = SendRequest(RequestMethod.Post,
                                        "https://api.twitter.com/1/friendships/create.xml",
                                        false,
                                        new KeyValuePair<string, string>("screen_name", user.ScreenName),
                                        new KeyValuePair<string, string>("follow", allowDeviceNotifications.ToString()));
            var doc = XDocument.Parse(result);

            return ParseAuthor(doc.Element("user"));
        } 
        #endregion

        #region UnfollowUser
        /// <summary>
        /// Allows the authenticating users to unfollow the specified user.
        /// </summary>
        /// <param name="screenName">The screen name of the user to follow</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>Unfollowed user</returns>
        public TwitterUser UnfollowUser(string screenName)
        {
            if (string.IsNullOrEmpty(screenName)) throw new ArgumentNullException("screenName");

            var result = SendRequest(RequestMethod.Delete,
                                        "https://api.twitter.com/1/friendships/destroy.xml",
                                        false,
                                        new KeyValuePair<string, string>("scren_name", screenName));
            var doc = XDocument.Parse(result);

            return ParseAuthor(doc.Element("user"));
        }

        /// <summary>
        /// Allows the authenticating users to unfollow the specified user.
        /// </summary>
        /// <param name="userId">The ID of the user to follow</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>Unfollowed user</returns>
        public TwitterUser UnfollowUser(ulong userId)
        {
            if (userId < 1) throw new ArgumentOutOfRangeException("userId");

            var result = SendRequest(RequestMethod.Delete,
                                        "https://api.twitter.com/1/friendships/destroy.xml",
                                        false,
                                        new KeyValuePair<string, string>("user_id", userId.ToString()));
            var doc = XDocument.Parse(result);

            return ParseAuthor(doc.Element("user"));
        }

        /// <summary>
        /// Allows the authenticating users to unfollow the specified user.
        /// </summary>
        /// <param name="user">The user whom to follow</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>Unfollowed user</returns>
        public TwitterUser UnfollowUser(TwitterUser user)
        {
            if (user == null) throw new ArgumentNullException("user");
            var result = SendRequest(RequestMethod.Delete,
                                        "https://api.twitter.com/1/friendships/destroy.xml",
                                        false,
                                        new KeyValuePair<string, string>("scren_name", user.ScreenName));
            var doc = XDocument.Parse(result);

            return ParseAuthor(doc.Element("user"));
        } 
        #endregion

        #region TestFollowing
        /// <summary>
        /// Test for the existence of friendship between two users.
        /// </summary>
        /// <param name="screenNameA">The screen name of the subject user</param>
        /// <param name="screenNameB">The screen name of the user to test for following user A</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>True if user_a follows user_b, otherwise false</returns>
        public bool TestFollowing(string screenNameA, string screenNameB)
        {
            if (string.IsNullOrEmpty(screenNameA)) throw new ArgumentNullException("screenNameA");
            if (string.IsNullOrEmpty(screenNameB)) throw new ArgumentNullException("screenNameB");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/friendships/exists.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_a", screenNameA),
                                        new KeyValuePair<string, string>("user_b", screenNameB));
            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return bool.Parse(doc.Element("friends").Value);
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Test for the existence of friendship between two users.
        /// </summary>
        /// <param name="userIdA">The ID of the subject user</param>
        /// <param name="userIdB">The ID of the user to test for following user A</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>True if user_a follows user_b, otherwise false</returns>
        public bool TestFollowing(ulong userIdA, ulong userIdB)
        {
            if (userIdA < 1) throw new ArgumentOutOfRangeException("userIdA");
            if (userIdB < 1) throw new ArgumentOutOfRangeException("userIdB");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/friendships/exists.xml",
                                        true,
                                        new KeyValuePair<string, string>("user_a", userIdA.ToString()),
                                        new KeyValuePair<string, string>("user_b", userIdB.ToString()));
            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return bool.Parse(doc.Element("friends").Value);
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Test for the existence of friendship between two users.
        /// </summary>
        /// <param name="userA">The subject user</param>
        /// <param name="userB">The user to test for following user A</param>
        /// <returns>True if user_a follows user_b, otherwise false</returns>
        /// <exception cref="ArgumentNullException"><paramref name="userA" /> is <c>null</c>.</exception>
        public bool TestFollowing(TwitterUser userA, TwitterUser userB)
        {
            if (userA == null) throw new ArgumentNullException("userA");
            if (userB == null) throw new ArgumentNullException("userB");

            return TestFollowing(userA.ScreenName, userB.ScreenName);
        } 
        #endregion

        #region ShowFriendship
        /// <summary>
        /// Returns detailed information about the relationship between two users.
        /// </summary>
        /// <param name="screenNameSource">The screen name of the subject user</param>
        /// <param name="screenNameTarget">The screen name of the target user</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <returns>Descriptor of a relationship</returns>
        public TwitterRelationship ShowFriendship(string screenNameSource, string screenNameTarget)
        {
            if (string.IsNullOrEmpty(screenNameSource)) throw new ArgumentNullException("screenNameSource");
            if (string.IsNullOrEmpty(screenNameTarget)) throw new ArgumentNullException("screenNameTarget");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/friendships/show.xml",
                                        true,
                                        new KeyValuePair<string, string>("source_screen_name", screenNameSource),
                                        new KeyValuePair<string, string>("target_screen_name", screenNameTarget));
            var doc = XDocument.Parse(result);

            return ParseRelationship(doc.Element("relationship"));
        }

        /// <summary>
        /// Returns detailed information about the relationship between two users.
        /// </summary>
        /// <param name="userIdSource">The user ID of the subject user</param>
        /// <param name="userIdTarget">The user ID of the target user</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>Descriptor of a relationship</returns>
        public TwitterRelationship ShowFriendship(ulong userIdSource, ulong userIdTarget)
        {
            if (userIdSource < 1) throw new ArgumentOutOfRangeException("userIdSource");
            if (userIdTarget < 1) throw new ArgumentOutOfRangeException("userIdTarget");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/friendships/show.xml",
                                        true,
                                        new KeyValuePair<string, string>("source_id", userIdSource.ToString()),
                                        new KeyValuePair<string, string>("target_id", userIdTarget.ToString()));
            var doc = XDocument.Parse(result);

            return ParseRelationship(doc.Element("relationship"));
        }

        /// <summary>
        /// Returns detailed information about the relationship between two users.
        /// </summary>
        /// <param name="userSource">The subject user</param>
        /// <param name="userTarget">The target user</param>
        /// <returns>Descriptor of a relationship</returns>
        /// <exception cref="ArgumentNullException"><paramref name="userSource" /> is <c>null</c>.</exception>
        public TwitterRelationship ShowFriendship(TwitterUser userSource, TwitterUser userTarget)
        {
            if (userSource == null) throw new ArgumentNullException("userSource");
            if (userTarget == null) throw new ArgumentNullException("userTarget");

            return ShowFriendship(userSource.ScreenName, userTarget.ScreenName);
        } 
        #endregion

        #region GetIncomingPendingRequests
        /// <summary>
        /// Returns a list of numeric IDs for every user who has a pending request to follow the authenticating user.
        /// </summary>
        /// <returns>List of IDs</returns>
        public Collection<ulong> GetIncomingPendingRequests()
        {
            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/friendships/incoming.xml",
                                        true);

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<ulong>(doc.Element("id_list").Element("ids").Elements("id").Select(idElement => ulong.Parse(idElement.Value)).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Returns a list of numeric IDs for every user who has a pending request to follow the authenticating user.
        /// </summary>
        /// <param name="cursor">Breaks the results into pages. Provide a value of -1 to begin paging.</param>
        /// <param name="previousCursor">Start index of the previous page</param>
        /// <param name="nextCursor">Start index of the next page</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <returns>List of IDs</returns>
        public Collection<ulong> GetIncomingPendingRequests(long cursor, out long previousCursor, out long nextCursor)
        {
            if (cursor < -1) throw new ArgumentOutOfRangeException("cursor");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/friendships/incoming.xml",
                                        true,
                                        new KeyValuePair<string, string>("cursor", cursor.ToString()));

            var doc = XDocument.Parse(result);
            var idsElement = doc.Element("id_list");
            var list = new Collection<ulong>();
            previousCursor = 0;
            nextCursor = 0;
            if (idsElement != null)
            {
                // ReSharper disable PossibleNullReferenceException
                list = new Collection<ulong>(idsElement.Element("ids").Elements("id").Select(idElement => ulong.Parse(idElement.Value)).ToList());

                previousCursor = long.Parse(idsElement.Element("previous_cursor").Value);
                nextCursor = long.Parse(idsElement.Element("next_cursor").Value);
                // ReSharper restore PossibleNullReferenceException
            }

            return list;
        } 
        #endregion

        #region GetOutgoingPendingRequests
        /// <summary>
        /// Returns an array of numeric IDs for every protected user for whom the authenticating user has a pending follow request.
        /// </summary>
        /// <returns>List of IDs</returns>
        public Collection<ulong> GetOutgoingPendingRequests()
        {
            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/friendships/outgoing.xml",
                                        true);

            var doc = XDocument.Parse(result);

            // ReSharper disable PossibleNullReferenceException
            return new Collection<ulong>(doc.Element("id_list").Element("ids").Elements("id").Select(idElement => ulong.Parse(idElement.Value)).ToList());
            // ReSharper restore PossibleNullReferenceException
        }

        /// <summary>
        /// Returns a list of numeric IDs for every protected user for whom the authenticating user has a pending follow request.
        /// </summary>
        /// <param name="cursor">Breaks the results into pages. Provide a value of -1 to begin paging.</param>
        /// <param name="previousCursor">Start index of the previous page</param>
        /// <param name="nextCursor">Start index of the next page</param>
        /// <returns>List of IDs</returns>
        /// <exception cref="ArgumentOutOfRangeException"><c>cursor</c> is out of range.</exception>
        public Collection<ulong> GetOutgoingPendingRequests(long cursor, out long previousCursor, out long nextCursor)
        {
            if (cursor < -1) throw new ArgumentOutOfRangeException("cursor");

            var result = SendRequest(RequestMethod.Get,
                                        "https://api.twitter.com/1/friendships/outgoing.xml",
                                        true,
                                        new KeyValuePair<string, string>("cursor", cursor.ToString()));

            var doc = XDocument.Parse(result);
            var idsElement = doc.Element("id_list");
            var list = new Collection<ulong>();
            previousCursor = 0;
            nextCursor = 0;
            if (idsElement != null)
            {
                // ReSharper disable PossibleNullReferenceException
                list = new Collection<ulong>(idsElement.Element("ids").Elements("id").Select(idElement => ulong.Parse(idElement.Value)).ToList());

                previousCursor = long.Parse(idsElement.Element("previous_cursor").Value);
                nextCursor = long.Parse(idsElement.Element("next_cursor").Value);
                // ReSharper restore PossibleNullReferenceException
            }

            return list;
        } 
        #endregion

        #region LookupFriendsips
        /// <summary>
        /// Returns the relationship of the authenticating user to the list of up to 100 screen names
        /// </summary>
        /// <param name="screenNames">Screen names of the users whose relationship to the authenticating user to return</param>
        /// <returns>Dictionary of screenName/TwitterUserConnection</returns>
        /// <exception cref="ArgumentNullException">Argument is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><c>screenNames</c> is out of range.</exception>
        public Dictionary<string, TwitterUserConnection> LookupFriedships(params string[] screenNames)
        {
            if (screenNames == null || screenNames.Length == 0) throw new ArgumentNullException("screenNames");
            if (screenNames.Count() > 100) throw new ArgumentOutOfRangeException("screenNames");

            var result = SendRequest(RequestMethod.Get,
                                     "https://api.twitter.com/1/friendships/lookup.xml",
                                     true,
                                     new KeyValuePair<string, string>("screen_name", string.Join(",", screenNames)));

            var doc = XDocument.Parse(result);

            var root = doc.Element("relationships");
            var resultDic = new Dictionary<string, TwitterUserConnection>();
            // ReSharper disable PossibleNullReferenceException
            foreach (var relationshipElement in root.Elements("relationship"))
            {
                TwitterUserConnection connection;
                switch (relationshipElement.Element("connections").Element("connection").Value)
                {
                    case "following":
                        connection = TwitterUserConnection.Following;
                        break;
                    case "following_requested":
                        connection = TwitterUserConnection.FollowingRequested;
                        break;
                    case "followedBy":
                        connection = TwitterUserConnection.FollowedBy;
                        break;
                    default:
                        connection = TwitterUserConnection.None;
                        break;
                }
                resultDic.Add(relationshipElement.Element("screen_name").Value, connection);
            }
            // ReSharper restore PossibleNullReferenceException
            return resultDic;
        }

        /// <summary>
        /// Returns the relationship of the authenticating user to the list of up to 100 user IDs
        /// </summary>
        /// <param name="userIds">IDs of the users whose relationship to the authenticating user to return</param>
        /// <returns>Dictionary of screenName/TwitterUserConnection</returns>
        /// <exception cref="ArgumentNullException">Argument is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><c>userIds</c> is out of range.</exception>
        public Dictionary<ulong, TwitterUserConnection> LookupFriedships(params ulong[] userIds)
        {
            if (userIds == null || userIds.Length == 0) throw new ArgumentNullException("userIds");
            if (userIds.Count() > 100) throw new ArgumentOutOfRangeException("userIds");

            var result = SendRequest(RequestMethod.Get,
                                     "https://api.twitter.com/1/friendships/lookup.xml",
                                     true,
                                     new KeyValuePair<string, string>("user_id", string.Join(",", userIds.Cast<string>().ToArray())));

            var doc = XDocument.Parse(result);

            var root = doc.Element("relationships");
            var resultDic = new Dictionary<ulong, TwitterUserConnection>();
            // ReSharper disable PossibleNullReferenceException
            foreach (var relationshipElement in root.Elements("relationship"))
            {
                TwitterUserConnection connection;
                switch (relationshipElement.Element("connections").Element("connection").Value)
                {
                    case "following":
                        connection = TwitterUserConnection.Following;
                        break;
                    case "following_requested":
                        connection = TwitterUserConnection.FollowingRequested;
                        break;
                    case "followedBy":
                        connection = TwitterUserConnection.FollowedBy;
                        break;
                    default:
                        connection = TwitterUserConnection.None;
                        break;
                }
                resultDic.Add(ulong.Parse(relationshipElement.Element("user_id").Value), connection);
            }
            // ReSharper restore PossibleNullReferenceException
            return resultDic;
        }

        /// <summary>
        /// Returns the relationship of the authenticating user to the list of up to 100 screen names
        /// </summary>
        /// <param name="users">The users whose relationship to the authenticating user to return</param>
        /// <returns>Dictionary of screenName/TwitterUserConnection</returns>
        /// <exception cref="ArgumentNullException">Argument is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><c>users</c> is out of range.</exception>
        public Dictionary<string, TwitterUserConnection> LookupFriedships(params TwitterUser[] users)
        {
            if (users == null || users.Length == 0) throw new ArgumentNullException("users");
            if (users.Count() > 100) throw new ArgumentOutOfRangeException("users");

            return LookupFriedships(users.Select(u => u.ScreenName).ToArray());
        } 
        #endregion

        /// <summary>
        /// Allows to enable or disable retweets and device notifications from the specified user.
        /// </summary>
        /// <param name="deviceNotifications">Desired device notifications status</param>
        /// <param name="retweets">?</param>
        public void UpdateFriendship(bool deviceNotifications, bool retweets)
        {
            SendRequest(RequestMethod.Post,
                                     "https://api.twitter.com/1/friendships/update.xml",
                                     true,
                                     new KeyValuePair<string, string>("device", deviceNotifications.ToString().ToLower()),
                                     new KeyValuePair<string, string>("retweets", retweets.ToString().ToLower()));
        }

        #endregion

        /// <exception cref="TwitterException"><c>TwitterException</c>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="relElement" /> is <c>null</c>.</exception>
        private static TwitterRelationship ParseRelationship(XContainer relElement)
        {
            TwitterRelationship result = null;
            if (relElement == null) throw new ArgumentNullException("relElement");
            var sourceElement = relElement.Element("source");
            if (sourceElement != null)
            {
                try
                {
                    // ReSharper disable PossibleNullReferenceException
                    var source = new TwitterRelationshipSource(sourceElement.Element("screen_name").Value,
                                                                       ulong.Parse(sourceElement.Element("id").Value),
                                                                       bool.Parse(sourceElement.Element("all_replies").Value),
                                                                       bool.Parse(sourceElement.Element("followed_by").Value),
                                                                       bool.Parse(sourceElement.Element("notifications_enabled").Value),
                                                                       bool.Parse(sourceElement.Element("following").Value),
                                                                       bool.Parse(sourceElement.Element("want_retweets").Value));

                    var target = new TwitterRelationshipTarget(sourceElement.Element("screen_name").Value,
                                                               ulong.Parse(sourceElement.Element("id").Value),
                                                               bool.Parse(sourceElement.Element("followed_by").Value),
                                                               bool.Parse(sourceElement.Element("following").Value));
                    // ReSharper restore PossibleNullReferenceException

                    result = new TwitterRelationship(source, target);
                }
                catch (NullReferenceException)
                {
                    throw new TwitterException(string.Empty);
                }
            }
            return result;
        }
    }
}
